package com.github.satant.utils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

public class ReflectionUtils {

  private static final String SETTER_PREFIX = "set";
  private static final String GETTER_PREFIX = "get";
  private static final String CGLIB_CLASS_SEPARATOR = "$$";
  private static Logger logger = LoggerFactory.getLogger(ReflectionUtils.class);

  public static Object invokeGetter(Object obj, String propertyName) {
    Object object = obj;
    for (String name : StringUtils.split(propertyName, ".")) {
      String getterMethodName = "get" + StringUtils.capitalize(name);
      object = invokeMethod(object, getterMethodName, new Class[0], new Object[0]);
    }
    return object;
  }

  public static void invokeSetter(Object obj, String propertyName, Object value) {
    Object object = obj;
    String[] names = StringUtils.split(propertyName, ".");
    for (int i = 0; i < names.length; ++i) {
      if (i < names.length - 1) {
        String getterMethodName = "get" + StringUtils.capitalize(names[i]);
        object = invokeMethod(object, getterMethodName, new Class[0], new Object[0]);
      } else {
        String setterMethodName = "set" + StringUtils.capitalize(names[i]);
        invokeMethodByName(object, setterMethodName, new Object[]{value});
      }
    }
  }

  public static Object getFieldValue(Object obj, String fieldName) {
    Field field = getAccessibleField(obj, fieldName);

    if (field == null) {
      throw new IllegalArgumentException(
          "Could not find field [" + fieldName + "] on target [" + obj + "]");
    }

    Object result = null;
    try {
      result = field.get(obj);
    } catch (IllegalAccessException e) {
      logger.error("不可能抛出的异常{}", e.getMessage());
    }
    return result;
  }

  public static void setFieldValue(Object obj, String fieldName, Object value) {
    Field field = getAccessibleField(obj, fieldName);

    if (field == null) {
      logger.error("Could not find field [" + fieldName + "] on target [" + obj + "]");
      return;
    }
    try {
      field.set(obj, convert(value, field.getType()));
    } catch (IllegalAccessException e) {
      logger.error("不可能抛出的异常:{}", e.getMessage());
    }
  }

  public static Object convert(Object object, Class<?> type) {
    if (object instanceof Number) {
      Number number = (Number) object;
      if ((type.equals(Byte.TYPE)) || (type.equals(Byte.class))) {
        return Byte.valueOf(number.byteValue());
      }
      if ((type.equals(Short.TYPE)) || (type.equals(Short.class))) {
        return Short.valueOf(number.shortValue());
      }
      if ((type.equals(Integer.TYPE)) || (type.equals(Integer.class))) {
        return Integer.valueOf(number.intValue());
      }
      if ((type.equals(Long.TYPE)) || (type.equals(Long.class))) {
        return Long.valueOf(number.longValue());
      }
      if ((type.equals(Float.TYPE)) || (type.equals(Float.class))) {
        return Float.valueOf(number.floatValue());
      }
      if ((type.equals(Double.TYPE)) || (type.equals(Double.class))) {
        return Double.valueOf(number.doubleValue());
      }
    }
    if (type.equals(String.class)) {
      return ((object == null) ? "" : object.toString());
    }
    return object;
  }

  public static Object invokeMethod(Object obj, String methodName, Class<?>[] parameterTypes,
      Object[] args) {
    Method method = getAccessibleMethod(obj, methodName, parameterTypes);
    if (method == null) {
      throw new IllegalArgumentException(
          "Could not find method [" + methodName + "] on target [" + obj + "]");
    }
    try {
      return method.invoke(obj, args);
    } catch (Exception e) {
      throw convertReflectionExceptionToUnchecked(e);
    }
  }

  public static Object invokeMethodByName(Object obj, String methodName, Object[] args) {
    Method method = getAccessibleMethodByName(obj, methodName);
    if (method == null) {
      throw new IllegalArgumentException(
          "Could not find method [" + methodName + "] on target [" + obj + "]");
    }
    try {
      return method.invoke(obj, args);
    } catch (Exception e) {
      throw convertReflectionExceptionToUnchecked(e);
    }
  }

  public static Field getAccessibleField(Object obj, String fieldName) {
    Validate.notNull(obj, "object can't be null", new Object[0]);
    Validate.notBlank(fieldName, "fieldName can't be blank", new Object[0]);
    for (Class superClass = obj.getClass(); superClass != Object.class;
        superClass = superClass.getSuperclass()) {
      try {
        Field field = superClass.getDeclaredField(fieldName);
        makeAccessible(field);
        return field;
      } catch (NoSuchFieldException e) {
      }
    }
    return null;
  }

  public static Method getAccessibleMethod(Object obj, String methodName,
      Class<?>[] parameterTypes) {
    Validate.notNull(obj, "object can't be null", new Object[0]);
    Validate.notBlank(methodName, "methodName can't be blank", new Object[0]);

    for (Class searchType = obj.getClass(); searchType != Object.class;
        searchType = searchType.getSuperclass()) {
      try {
        Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
        makeAccessible(method);
        return method;
      } catch (NoSuchMethodException e) {
      }
    }
    return null;
  }

  public static Method getAccessibleMethodByName(Object obj, String methodName) {
    Validate.notNull(obj, "object can't be null", new Object[0]);
    Validate.notBlank(methodName, "methodName can't be blank", new Object[0]);

    for (Class searchType = obj.getClass(); searchType != Object.class;
        searchType = searchType.getSuperclass()) {
      Method[] methods = searchType.getDeclaredMethods();
      for (Method method : methods) {
        if (method.getName().equals(methodName)) {
          makeAccessible(method);
          return method;
        }
      }
    }
    return null;
  }

  public static void makeAccessible(Method method) {
    if (((Modifier.isPublic(method.getModifiers())) && (Modifier
        .isPublic(method.getDeclaringClass().getModifiers()))) || (method.isAccessible())) {
      return;
    }
    method.setAccessible(true);
  }

  public static void makeAccessible(Field field) {
    if (((Modifier.isPublic(field.getModifiers())) && (Modifier
        .isPublic(field.getDeclaringClass().getModifiers())) && (!(Modifier
        .isFinal(field.getModifiers())))) || (field.isAccessible())) {
      return;
    }
    field.setAccessible(true);
  }

  public static <T> Class<T> getClassGenricType(Class clazz) {
    return getClassGenricType(clazz, 0);
  }

  public static Class getClassGenricType(Class clazz, int index) {
    Type genType = clazz.getGenericSuperclass();

    if (!(genType instanceof ParameterizedType)) {
      logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
      return Object.class;
    }

    Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

    if ((index >= params.length) || (index < 0)) {
      logger.warn(
          "Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
              + params.length);

      return Object.class;
    }
    if (!(params[index] instanceof Class)) {
      logger.warn(
          clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
      return Object.class;
    }

    return ((Class) params[index]);
  }

  public static Class<?> getUserClass(Object instance) {
    Assert.notNull(instance, "Instance must not be null");
    Class clazz = instance.getClass();
    if ((clazz != null) && (clazz.getName().contains("$$"))) {
      Class superClass = clazz.getSuperclass();
      if ((superClass != null) && (!(Object.class.equals(superClass)))) {
        return superClass;
      }
    }
    return clazz;
  }

  public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
    if ((e instanceof IllegalAccessException) || (e instanceof IllegalArgumentException)
        || (e instanceof NoSuchMethodException)) {
      return new IllegalArgumentException(e);
    }
    if (e instanceof InvocationTargetException) {
      return new RuntimeException(((InvocationTargetException) e).getTargetException());
    }
    if (e instanceof RuntimeException) {
      return ((RuntimeException) e);
    }
    return new RuntimeException("Unexpected Checked Exception.", e);
  }

  public static boolean hasField(Object obj, String fieldName) {
    Field field = getAccessibleField(obj, fieldName);

    return (field != null);
  }
}